Trabajo Preparacion, visualizacion de Datos y Machine learning con Python

Ciencia de datos en Produccion

Estudiante: Sebastián Cardona y Jose Miguel Millán

ID: 1094910122 y 1088334182

Email: sacardonar@uqvirtual.edu.co y josem.millanl@uqvirtual.edu.co

**Ponga su nombre en el archivo de Jupyter Notebook Docente: [Jose R. Zapata](https://joserzapata.github.io)

  • https://joserzapata.github.io
  • https://twitter.com/joserzapata
  • https://www.linkedin.com/in/jose-ricardo-zapata-gonzalez/

Contenido

  • 1  Definir el Problema a Resolver
    • 1.1  Describir los datos de entrada y salida
  • 2  Importar Librerias
  • 3  Cargar Datasets
  • 4  Descripcion y Limpieza de los datos
    • 4.1  Identificacion de Variables
    • 4.2  Analisis General Univariable y Bivariable
    • 4.3  Eliminar columnas de datos Innecesarios
    • 4.4  Remover Datos Duplicados Exactos
    • 4.5  Procesamiento de Datos Faltantes
      • 4.5.1  Borrar Filas
      • 4.5.2  Reemplazar datos faltantes con la Media/ Moda/ Mediana (Mean/ Mode/ Median Imputation)
    • 4.6  Remover Datos Duplicados Exactos
    • 4.7  Analisis Univariable
      • 4.7.1  Variables Numericas
      • 4.7.2  Variables Categoricas
    • 4.8  Analisis Bivariable
      • 4.8.1  Numericas vs Numericas
      • 4.8.2  Categoricas vs Categoricas
      • 4.8.3  Categoricas vs Numericas
    • 4.9  Procesamiento de Outliers
      • 4.9.1  Deteccion de Outliers (Univariables y Bi variables)
      • 4.9.2  Remover Outliers
    • 4.10  Procesamiento de Outliers
      • 4.10.1  Deteccion de Outliers (Univariables y Bi variables)
      • 4.10.2  Remover Outliers
    • 4.11  Feature Engineering
      • 4.11.1  Transformacion de Variables
        • 4.11.1.1  Normalizacion
        • 4.11.1.2  Escalamiento
        • 4.11.1.3  Logaritmica
        • 4.11.1.4  raíz cuadrada / cúbica
        • 4.11.1.5  Binning , Cambios de Numericas a Categoricas
    • 4.12  Analisis Univariable y Bivariable Final
      • 4.12.1  Creacion de Variables
        • 4.12.1.1  Crear Variables derivadas de Otras
        • 4.12.1.2  Crear Variables de Categorico a Numerico
    • 4.13  Reduccion de Dimensionalidad y Seleccion de Variables (PCA)
    • 4.14  Balance de datos
  • 5  MODELAMIENTO DE LOS DATOS (MACHINE LEARNING)
    • 5.1  Dividir el dataset en Training set y Test set
  • 6  Validacion y Evaluacion Cruzada (k-fold Cross Validation)
  • 7  Optimizacion de Hiper parametros (Hyper Parameter optimization)
  • 8  Evaluacion final del modelo con el Test set
  • 9  Implementacion del Modelo (Deploying)
  • 10  Comunicacion de Resultados (Data Story Telling)
  • 11  Conclusiones
  • 12  Ayudas Y Referencias

Objetivo del Trabajo

El objetivo inicial del trabajo es utilizar Python para el procesamiento, descripcion y visualización de datos, con el fin de dejar los datos preparados para ser usados con algoritmos de Machine Learning para Regresión o Clasificación como objetivo final del trabajo.

El trabajo se realizara en Python usando un jupyter notebook o un Notebook de Google Collaboratory, llenando los campos de este archivo y subirlo a github.

Las actividades a realizar

  1. Preparar los datos, hacer cada uno de los pasos solo si es necesario:

    • Poner los datos en sus tipos de datos correctos
    • Remover los valores duplicados
    • Procesar y Reemplazar los datos que faltan (Missing N.A values)
    • Manejo de outliers
    • Re-escalar las variables
    • Normalizar
    • Data Binning
      • Conversión Numérico a categórico
      • Conversión Categórico a Numérico
  2. Realizar una exploración estadística y con visualización de los datos

    • Scatter plots
    • Histogramas
    • boxplot, o las gráficas que considere necesarias
  3. Entrene y Evalue modelos de Machine Learning según sea su problema, recuerde usar k-fold cross-validation para evitar over fitting, algunos algoritmos recomendados

    (a) PREDICCIÓN:

         ▪ Regresión Lineal
         ▪ Lasso
         ▪ Ridge
         ▪ Decision Tree regressor
         ▪ Random Forest regressor
         ▪ SVR
         ▪ KNR
         ▪ catboost regressor
    
    
    
    

    (b) CLASIFICACION

         ▪ Regresión Logística
         ▪ LinearSVC
         ▪ KernelSVC
         ▪ Decision Tree clasifier
         ▪ Random Forest classifier
         ▪ K-NN
         ▪ GaussianNB
         ▪ catboost
    
    

Nota: utilizar visualizaciones para comunicar los resultados obtenidos.

  1. Realizar hyperparameter optimization de los dos métodos que tengan mejor resultado en los pasos anteriores para mejorar el desempeño obtenido. (Tenga en cuenta, que realizar el hyperparameter tuning no garantiza una mejora significativa en el desempeño, pero es bueno intentarlo)

  2. Realizar comentarios de cada paso que va realizando en el documento y finalmente también agregue conclusiones de:

    • los datos procesados
    • la información que observa
    • Comparación de la evaluación de los Métodos
    • Cual fue el mejor modelo que obtuvo luego de la hiper parametrización

*NOTA: No dude en contactarme para cualquier pregunta o inquietud :)

FORMATO DE ENTREGA

EL jupyter notebook estara alojado en un repositorio de Github

El código debe tener comentarios y explicaciones de la solución del trabajo.

EVALUACION

(33 % ) preparacion, descripcion y visualizacion de datos

(33 % ) Machine Learning, hiperparametrizacion y Conclusiones

|Porcentaje en la evaluación | Descripción| Nada | Incompleto | Completo | | :---: |:---: |:---: |:---: |:---: | | 11 % |Preparación de los datos
(datos nulos, outliers, duplicados,
corrección de formatos de tipos de datos) | |11 % | Visualización de datos
(Univarible y Bivariable)
Histogramas, boxplots, correlaciones, etc| |11 % | Descripción y Análisis Estadístico de los datos | |11 % | Machine Learning
Entrenar y evaluar todos los modelos propuestos | |11 % | Hiper parametrizacion
Hiperparametrizar 2 modelos y escoger el mejor modelo |
|11 % | Resultados y Conclusiones
analisis de los datos, conclusiones del modelos y los resultados obtenidos|

Ejemplos y links de ayuda al final del documento

Definir el Problema a Resolver¶

El dataset "house data", inicialmente se realizará una exploración de datos, para poder saber la calidad del dataset, iniciando con una limpieza la cual consta de eliminar duplicados, identificación de datos atípicos, nullos o mal escritos para poder tratarlos y mitigarlos, ya sea con la eliminación o aplicación de métodos estadísticos, con la finalidad de tener un datset listo y poder aplicar una regresión lineal y poder predecir los precios de venta de una casa.

Describir los datos de entrada y salida¶

  • Cantidad de Variables
  • Tipo de Variables
  • Significado de cada Variable

Importar Librerias¶

In [1]:
from jutils.data import DataUtils
from jutils.visual import Plot
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from pandas_profiling.profile_report import ProfileReport
from pathlib import Path
import pandas as pd
import numpy as np
from cache_to_disk import cache_to_disk
from IPython.display import display, HTML
import plotly.express as px
import seaborn as sns
from sklearn.preprocessing import PowerTransformer, KBinsDiscretizer, OneHotEncoder
from scipy import stats
import pingouin as pg
from sklearn.linear_model import SGDRegressor
from sklearn.svm import SVR
from sklearn.model_selection import cross_val_score
In [2]:
def validar_duplicados(_df):
    _filas = _df.shape[0]
    _cant_duplicados = _df.duplicated().sum()
    print(f'De {_filas} registros hay {_cant_duplicados} filas duplicadas, representando el {_cant_duplicados/_filas:.2%}')

def eliminar_duplicados(_df):
    # Eliminando duplicados
    _df = _df.drop_duplicates(keep='first')
    _filas = _df.shape[0]
    print(f'Después de la eliminación de duplicados, el conjunto de datos queda con {_filas} filas.')
    return _df

def validar_index_duplicados(_df):
    # Validando duplicados de index
    _son_duplicados = _df['index'].duplicated()
    _cant_duplicados = _son_duplicados.sum()
    _filas = _df.shape[0]
    print(f'De {_filas} registros, hay {_cant_duplicados} registros con index duplicado, que representan el {_cant_duplicados/_filas:.2%}.')
    return _son_duplicados

@cache_to_disk(1)
def profiler_to_file(_profiler, archivo):
    print('Ejecutando profiler')
    _profiler.to_file(du.data_folder_path.parent.joinpath('reports/' + archivo))
    return True

def calcular_descriptivas(_df):
    descriptivas = _df.describe()
    descriptivas.loc['rango'] = descriptivas.loc['max'] - descriptivas.loc['min']
    descriptivas.loc['IQR'] = descriptivas.loc['75%'] - descriptivas.loc['25%']
    descriptivas.loc['coef de var'] = descriptivas.loc['std']/descriptivas.loc['mean']
    descriptivas.loc['skewness'] = du.data.skew(numeric_only=True)
    descriptivas.loc['kurtosis'] = du.data.kurtosis(numeric_only=True)
    return descriptivas

Cargar Datasets¶

In [3]:
du = DataUtils(
    Path(r'..\data').resolve().absolute(),
    "kc_house_dataDS.parquet",
    lambda path: pd.read_parquet(path),
    lambda df, path: df.to_parquet(path)
)
du.data = du.load_data(du.interim_path.joinpath(du.input_file_name))

Descripcion General del Dataset¶

  • numero de filas y columnas
  • tipos de datos y si estan correctos

Durante la exploración inicial se realizó la conversión de los tipos de datos, y la correcta representación de datos nulos.

In [4]:
shape = du.data.shape
filas = shape[0]
columnas = shape[1]
print(f'El conjunto de datos se compone de {filas} filas y {columnas} columnas.')
El conjunto de datos se compone de 131994 filas y 23 columnas.
In [5]:
du.data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 131994 entries, 0 to 131993
Data columns (total 23 columns):
 #   Column         Non-Null Count   Dtype  
---  ------         --------------   -----  
 0   index          131994 non-null  int64  
 1   zipcode        118787 non-null  float64
 2   grade          118717 non-null  float64
 3   sqft_basement  118688 non-null  float64
 4   view           118854 non-null  float64
 5   bathrooms      118689 non-null  float64
 6   bedrooms       118729 non-null  float64
 7   sqft_above     118712 non-null  float64
 8   sqft_living15  118814 non-null  float64
 9   lat            118731 non-null  float64
 10  waterfront     118730 non-null  float64
 11  floors         118791 non-null  float64
 12  date           127544 non-null  object 
 13  yr_renovated   118728 non-null  float64
 14  yr_built       118712 non-null  float64
 15  long           118760 non-null  float64
 16  jhygtf         118728 non-null  float64
 17  sqft_lot       118763 non-null  float64
 18  price          118661 non-null  float64
 19  condition      118740 non-null  float64
 20  wertyj         131994 non-null  int64  
 21  sqft_lot15     118739 non-null  float64
 22  sqft_living    118768 non-null  float64
dtypes: float64(20), int64(2), object(1)
memory usage: 24.2+ MB

Todas las columnas son del tipo correcto a excepción de date, se deberá hacer la conversión de este campo.

Limpieza de calidad de datos general¶

  • Filas con valores exactamente iguales (duplicados)
  • Columnas duplicadas
  • Columnas con valores constantes o sin informacion
In [6]:
validar_duplicados(du.data)
De 131994 registros hay 1300 filas duplicadas, representando el 0.98%
In [7]:
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 130694 filas.
In [8]:
# Validar indices duplicados
son_duplicados = validar_index_duplicados(du.data)
De 130694 registros, hay 108695 registros con index duplicado, que representan el 83.17%.
In [9]:
# Revisando los primeros registros duplicados
du.data[son_duplicados].sort_values(by='index').head()
Out[9]:
index zipcode grade sqft_basement view bathrooms bedrooms sqft_above sqft_living15 lat ... yr_renovated yr_built long jhygtf sqft_lot price condition wertyj sqft_lot15 sqft_living
44480 0 98178.0 7.0 0.0 0.0 1.0 NaN 1.180000e+03 1.340000e+03 47.5112 ... 0.0 1.955000e+03 NaN 0.0 5.650000e+03 221900.0 3.000000e+00 876543 5650.0 1180.0
59332 0 98178.0 7.0 NaN NaN 1.0 3.0 1.180000e+03 1.340000e+03 47.5112 ... 0.0 1.955000e+03 -122257.0 0.0 5.650000e+03 NaN 3.000000e+00 876543 5650.0 1180.0
89550 0 NaN NaN 0.0 0.0 1.0 3.0 1.180000e+03 NaN NaN ... 0.0 NaN -122257.0 0.0 5.650000e+03 221900.0 3.000000e+00 876543 5650.0 1180.0
27240 0 98178.0 7.0 0.0 0.0 1.0 3.0 1.180000e+03 1.340000e+03 47.5112 ... 0.0 1.955000e+03 -122257.0 0.0 5.650000e+03 221900.0 3.000000e+00 876543 5650.0 1180.0
50303 0 98178.0 7.0 0.0 0.0 1.0 3.0 -5.432346e+10 -5.432346e+10 47.5112 ... 0.0 -5.432346e+10 -122257.0 0.0 -5.432346e+10 221900.0 -5.432346e+10 876543 5650.0 1180.0

5 rows × 23 columns

Revisando los registros duplicados por index, se encuentra que muchas columnas tienen los mismos valores , lo único que cambia es que hay algunos faltantes y hay otros valores extremadamente bajos o altos, adicionalmente se observan algunos registros de la columna date que no son fechas. Primero se convertirá los valores de la columna date a date y los que no puedan ser convertidos se reemplazarán por valores nulos, luego se reemplazarán los valores extremos por valores nulos, luego se calculará la mediana por index para las columnas numéricas y se reemplazarán los valores nulos por estas medianas. Luego se eliminarán filas duplicadas y se reevaluarán los index duplicados.

In [10]:
# Convirtiendo la columna date a datetime
du.data['date'] = pd.to_datetime(du.data['date'], errors='coerce')
In [11]:
# Reemplazando valores extremos, menores a -1e+10 o mayores a 1e+10
columnas_numericas = [columna for columna in du.data.columns if columna != 'date']
du.data[columnas_numericas] = du.data[columnas_numericas].where(lambda x: x > -1e+10, other=np.nan).where(lambda x: x < 1e+10, other=np.nan)
In [12]:
# Se reemplazan los valores extremos por la media
for columna_numerica in columnas_numericas:
    du.data[columna_numerica]=du.data[columna_numerica].fillna(du.data.groupby('index')[columna_numerica].transform('median'))
In [13]:
# Reemplazando fechas nulas por la primera fecha no nula
du.data['date'] = du.data['date'].fillna(du.data.groupby(['index'], sort=False)['date'].apply(lambda x: x.ffill().bfill()))

Para las siguientes columnas, un cero representa un dato nulo, por lo tanto se reemplazarán.

  • sqft_basement
  • yr_renovated
In [14]:
# Reemplazando ceros por valores nulos
du.data[['sqft_basement', 'yr_renovated']] = du.data[['sqft_basement', 'yr_renovated']].replace(0, np.nan)
In [15]:
validar_duplicados(du.data)
De 130694 registros hay 108695 filas duplicadas, representando el 83.17%
In [16]:
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 21999 filas.
In [17]:
son_duplicados = validar_index_duplicados(du.data)
De 21999 registros, hay 0 registros con index duplicado, que representan el 0.00%.
In [18]:
# Validando columnas con valores constantes
unicos=du.data.nunique()
unicos[unicos==1]
Out[18]:
wertyj    1
dtype: int64

La columna wertyj tiene valores constantes, por lo tanto se eliminará.

In [19]:
du.data.drop(columns=list(unicos[unicos==1].index), inplace=True)
In [20]:
nulos = du.data.isnull().sum()
porce = nulos/filas
nulos = pd.DataFrame({'nulos':nulos, 'porc':porce})
# Se contarán las filas que contengan algún dato nulo
al_menos_un_nulo=du.data.isnull().any(axis=1).sum()
nulos
Out[20]:
nulos porc
index 0 0.000000
zipcode 12 0.000091
grade 9 0.000068
sqft_basement 13368 0.101277
view 4 0.000030
bathrooms 8 0.000061
bedrooms 5 0.000038
sqft_above 12 0.000091
sqft_living15 1 0.000008
lat 9 0.000068
waterfront 6 0.000045
floors 9 0.000068
date 3 0.000023
yr_renovated 21069 0.159621
yr_built 7 0.000053
long 3 0.000023
jhygtf 9 0.000068
sqft_lot 6 0.000045
price 30 0.000227
condition 5 0.000038
sqft_lot15 1 0.000008
sqft_living 11 0.000083
In [21]:
print(f'De {filas} registros, hay {al_menos_un_nulo} registros con al menos un valor nulo, representando el {al_menos_un_nulo/filas:.2%}')
De 131994 registros, hay 21531 registros con al menos un valor nulo, representando el 16.31%

Dividir el dataset en Training set y Test set¶

Este paso debe ser al inicio de los proyectos, Se deben realizar todas las transformaciones, preparación de datos y limpieza de los datos, en el train set y en la evaluacion se deben aplicarl al test set y a los datos nuevos que lleguen al sistema. Esta division inicial se hace para evitar data leakage de los datos de test a los datos de train, por ejemplo en las imputaciones.

Por este motivo se realizara en esta parte.

Division de los datos de entrenamiento (Train set) y de Evaluacion (test - set), las divisiones utilizadas son

  • 80% (train) , 20%(test)
  • 70% (train) , 30%(test)

El Train set se hace para seleccion de Modelos y el Test-set solamente para la evaluacion Final

In [22]:
train_test, validation = train_test_split(du.data, test_size=0.8, random_state=1)
du.save_data(train_test, du.raw_train_test_path)
du.save_data(validation, du.raw_validation_path)

Descripcion y Limpieza de los datos¶

In [23]:
du.data = du.load_data(du.raw_train_test_path)
print('Tipos de variables')
du.data.info()
Tipos de variables
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4399 entries, 32828 to 235
Data columns (total 22 columns):
 #   Column         Non-Null Count  Dtype         
---  ------         --------------  -----         
 0   index          4399 non-null   int64         
 1   zipcode        4395 non-null   float64       
 2   grade          4398 non-null   float64       
 3   sqft_basement  1722 non-null   float64       
 4   view           4399 non-null   float64       
 5   bathrooms      4397 non-null   float64       
 6   bedrooms       4399 non-null   float64       
 7   sqft_above     4395 non-null   float64       
 8   sqft_living15  4398 non-null   float64       
 9   lat            4399 non-null   float64       
 10  waterfront     4399 non-null   float64       
 11  floors         4397 non-null   float64       
 12  date           4398 non-null   datetime64[ns]
 13  yr_renovated   212 non-null    float64       
 14  yr_built       4399 non-null   float64       
 15  long           4399 non-null   float64       
 16  jhygtf         4396 non-null   float64       
 17  sqft_lot       4398 non-null   float64       
 18  price          4396 non-null   float64       
 19  condition      4399 non-null   float64       
 20  sqft_lot15     4399 non-null   float64       
 21  sqft_living    4398 non-null   float64       
dtypes: datetime64[ns](1), float64(20), int64(1)
memory usage: 790.4 KB

Identificacion de Variables¶

  • Variables de entrada y de salida
  • Tipo de Variables (categoricas o Numericas)
  • Tipo de datos (int, float, string, factor, boolean, ...)
In [24]:
columnas_no_entrada = {'date', 'index'}
entradas = [column for column in du.data.columns if column not in columnas_no_entrada]
print('Columnas de entrada:')
print(', '.join(entradas))
Columnas de entrada:
zipcode, grade, sqft_basement, view, bathrooms, bedrooms, sqft_above, sqft_living15, lat, waterfront, floors, yr_renovated, yr_built, long, jhygtf, sqft_lot, price, condition, sqft_lot15, sqft_living
In [25]:
salida = 'date'
print(f'Columna de salida: {salida}')
Columna de salida: date

Analisis General Univariable y Bivariable¶

Analisis de cada una de las variables para lograr calidad de datos en cada columna

  • Correccion del tipo de dato (numericas, categoricas, string) de cada columna (optimizar memoria)
  • Deteccion de numero de datos faltantes
  • Deteccion de duplicados

Eliminar columnas de datos Innecesarios¶

Se realizará un perfilado de los datos para identificar problemas de calidad no identificados anteriormente.

In [26]:
columnas_entrenamiento = entradas + [salida]
profiler = ProfileReport(du.data[columnas_entrenamiento], explorative=True)

profiler_to_file(profiler, '1_0.html')
Out[26]:
True
In [27]:
graficar = Plot()
graficar.box(du.data, y='sqft_lot15')

| Columna | Tipo | Problemas de calidad encontrados | Correcciones | |---------------|--------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | df_index | | | | | zipcode | | | Se eliminará | | grade | Categórica | | | | sqft_basement | Numérica | Muchos datos nulos | Se encontró el 60.8% nulos, se transformará esta columna en una columna categórica binaria que diga si tiene sótano o no (Si el valor es nulo no tiene, de lo contrario si tiene). | | view | Categórica | | | | bathrooms | Numérica | | | | bedrooms | Numérica | | | | sqft_above | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | sqft_living15 | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | lat | | | Se eliminará, esta columna solo representa una posición, y como todas las casas están en la misma ciudad, es la misma latitud. | | waterfront | Categórica | | | | floors | Numérica | | | | date | fecha | | Se utilizará esta columna para calcular la cantidad de años transcurridos desde la construcción de la propiedad hasta la venta de la misma. | | yr_renovated | Numérica | | Se eliminará, será reemplazada por una columna que indique si fue renovada o no sin importar el año. | | yr_built | | | Se utilizará para calcular los años transcurridos desde la construcción hasta la venta de la propiedad, luego será eliminada. | | long | | | Se eliminará, esta columna solo representa una posición, y como todas las casas están en la misma ciudad, es la misma longitud. | | jhygtf | | | Es la misma columna que yr_renovated, será eliminada. | | sqft_lot | Numérica | Se encontraron datos atípicos. | Se encontró que el dato mas grande es de 1 millón de pies cuadrados que equivalen a aproximadamente 10 hectáreas, se considera que una propiedad puede tener estas dimensiones, por lo tanto no se considera un error. Se aplicará logaritmo y se validará si la distribución se normaliza, de lo contrario, se convertirá a categórica dividiéndola en rangos de tamaño. | | price | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | condition | Categórica nominal | | Se aplicará one hot encoding | | sqft_lot15 | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | sqft_living | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. |

Conversión tipos de variables¶

In [28]:
variables_categoricas = ['grade', 'view', 'waterfront', 'condition']
du.data[variables_categoricas] = du.data[variables_categoricas].astype("category")

Calculo de variables adicionales¶

In [29]:
# Años transcurridos entre la construcción de la vivienda y la venta de la misma columnas yr_built y date
# Primero se extraerá el año de la fecha para realizar la resta con yr_built
du.data['yr_date'] = du.data['date'].dt.year
du.data['antiguedad_venta'] = du.data['yr_date'] - du.data['yr_built']
du.data.drop(columns=['yr_date', 'date', 'yr_built'], inplace=True)

Eliminación de columnas¶

In [30]:
du.data.drop(columns=['zipcode', 'lat', 'yr_renovated', 'long', 'jhygtf'], inplace=True)
In [31]:
variables_numericas   = ['sqft_basement', 'bathrooms', 'bedrooms', 'sqft_above', 'sqft_living15', 'floors', 'sqft_lot', 'price', 'sqft_lot15',
                         'sqft_living', 'antiguedad_venta']

Procesamiento de Datos Faltantes¶

Las opciones que se pueden usar dependiendo del analisis de los datos son:

Borrar Filas¶

  • Borrar las filas que les falten todos los datos
  • Borrar Solo las filas que les falta las variables mas importantes o la salida
In [32]:
columnas_datosFaltantes = [columna for columna in du.data.columns if columna != 'date' and columna !=   'sqft_basement' and columna != 'yr_renovated' ]
du.data=du.data.dropna(subset=columnas_datosFaltantes)
du.data.isnull().sum().sort_values(ascending=False)
Out[32]:
sqft_basement       2668
index                  0
grade                  0
view                   0
bathrooms              0
bedrooms               0
sqft_above             0
sqft_living15          0
waterfront             0
floors                 0
sqft_lot               0
price                  0
condition              0
sqft_lot15             0
sqft_living            0
antiguedad_venta       0
dtype: int64

Analisis Univariable¶

Estadistico Descriptico y Analisis

Variables Numericas¶

| Tendencia Central | Medida de Dispersión | Visualizacion | |:-----------------:|:-------------------------:|:-------------:| | Media | Rango | Histogramas | | Mediana | Cuartiles | Boxplots | | Moda | Rango inter cuartil (IQR) | | | Minimo | Varianza | | | Maximo | Desviacion Estandard | | | . | Skewness | | | . | Kurtosis | |

In [33]:
calcular_descriptivas(du.data)
Out[33]:
index sqft_basement bathrooms bedrooms sqft_above sqft_living15 floors sqft_lot price sqft_lot15 sqft_living antiguedad_venta
count 4384.000000 1716.000000 4384.000000 4384.000000 4384.000000 4384.000000 4384.000000 4.384000e+03 4.384000e+03 4384.000000 4384.000000 4384.000000
mean 10927.939097 741.649184 2.111656 3.367245 1776.135493 1987.938869 1.484261 1.424680e+04 4.028694e+07 12266.174270 2066.434307 43.431113
std 6280.360000 399.057931 0.770968 0.907386 807.552700 680.565380 0.538801 3.647116e+04 2.463024e+08 26861.322559 897.047578 29.188176
min 1.000000 10.000000 0.500000 1.000000 420.000000 720.000000 1.000000 6.090000e+02 8.995000e+04 748.000000 420.000000 -1.000000
25% 5473.500000 450.000000 1.750000 3.000000 1200.000000 1500.000000 1.000000 5.081500e+03 3.199500e+05 5110.000000 1420.000000 18.000000
50% 10775.500000 700.000000 2.250000 3.000000 1560.000000 1830.000000 1.000000 7.681500e+03 4.500000e+05 7683.000000 1900.000000 40.000000
75% 16344.750000 970.000000 2.500000 4.000000 2190.000000 2360.000000 2.000000 1.068325e+04 6.500000e+05 10057.250000 2547.750000 63.000000
max 21985.000000 3480.000000 8.000000 9.000000 8570.000000 5790.000000 3.500000 1.074218e+06 3.635000e+09 871200.000000 12050.000000 115.000000
rango 21984.000000 3470.000000 7.500000 8.000000 8150.000000 5070.000000 2.500000 1.073609e+06 3.634910e+09 870452.000000 11630.000000 116.000000
IQR 10871.250000 520.000000 0.750000 1.000000 990.000000 860.000000 1.000000 5.601750e+03 3.300500e+05 4947.250000 1127.750000 45.000000
coef de var 0.574707 0.538068 0.365101 0.269474 0.454668 0.342347 0.363010 2.559954e+00 6.113704e+00 2.189870 0.434104 0.672057
skewness 0.032331 1.041309 0.549007 0.411545 1.371571 1.092855 0.650255 1.297772e+01 6.738575e+00 13.287885 1.388833 0.450191
kurtosis -1.189729 2.657962 1.625505 1.196312 3.100660 1.456801 -0.451702 2.691536e+02 4.994818e+01 304.005651 5.344829 -0.666037
In [34]:
for variable_numerica in variables_numericas:
    graficar.box(du.data, y=variable_numerica).show()

for variable_numerica in variables_numericas:
    graficar.histogram(du.data, x=variable_numerica, nbins=5).show()

Variables Categoricas¶

  • Numero de elementos por categoria
  • Porcentaje de elementos por categoria
  • Graficos de barras
In [35]:
resumen = []

for variable_categorica in variables_categoricas:
    col = du.data[variable_categorica]
    elms_cat = col.groupby(by=col).agg('count')
    total = elms_cat.sum()
    porc = elms_cat / total
    porc.name = 'porc'
    df = pd.DataFrame([elms_cat, porc]).transpose()
    resumen.append(df)

for tabla in resumen:
    display(HTML(tabla.to_html()))
    variable = tabla.columns[0]
    fig = px.bar(tabla[variable], orientation='h', title=str(variable))
    fig.show()
C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning:

Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.

C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning:

Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.

C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning:

Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.

C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning:

Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.

grade porc
grade
3.0 1.0 0.000228
4.0 3.0 0.000684
5.0 60.0 0.013686
6.0 407.0 0.092838
7.0 1828.0 0.416971
8.0 1228.0 0.280109
9.0 508.0 0.115876
10.0 254.0 0.057938
11.0 76.0 0.017336
12.0 16.0 0.003650
13.0 3.0 0.000684
view porc
view
0.0 3966.0 0.904653
1.0 72.0 0.016423
2.0 189.0 0.043111
3.0 90.0 0.020529
4.0 67.0 0.015283
waterfront porc
waterfront
0.0 4349.0 0.992016
1.0 35.0 0.007984
condition porc
condition
1.0 7.0 0.001597
2.0 35.0 0.007984
3.0 2884.0 0.657847
4.0 1112.0 0.253650
5.0 346.0 0.078923

Analisis Bivariable¶

Estadistico Descriptico y Analisis

Numericas vs Numericas¶

  • Scatter Plot
  • Heatmap
  • Correlacion
In [36]:
corr_matrix = du.data.corr()
In [37]:
corr_matrix['price'].sort_values(ascending=False)
Out[37]:
price               1.000000
sqft_living         0.298693
sqft_above          0.277249
sqft_living15       0.260434
bathrooms           0.220781
sqft_basement       0.144820
floors              0.126196
bedrooms            0.111953
sqft_lot15          0.024541
sqft_lot            0.016790
antiguedad_venta   -0.000036
index              -0.017693
Name: price, dtype: float64

Las variables relacionadas con el tamaño del apartamento tienen mayor correlación con el precio del apartamento, se observa que la antiguedad de venta, no tiene correlación con el precio, por lo tanto esta columna será eliminada.

In [38]:
du.data.drop(columns='antiguedad_venta', inplace=True)
# plot it
sns.heatmap(corr_matrix, cmap='PuOr')
Out[38]:
<AxesSubplot:>
In [39]:
fig, ax = plt.subplots(figsize=(12, 8))

plt.scatter(du.data['price'], du.data['bedrooms'])

plt.xlabel('precio')
plt.ylabel('cantidad de baños')
plt.show()
In [40]:
graficar.scatter(du.data,x='price',y='sqft_lot')

Se pueden resaltar dos datos atípicos, una casa muy económica y grande (USD 937000 y 871000 sqft) y una casa muy costosa y pequeña (USD 3000000000 y 19897 sqft), estos datos se consideran atípicos pero que pueden ocurrir en la realidad. Adicionalmente se encuentran dos grupos de apartamentos, el primer grupo son casas que cuestan menos de 8 millones y tienen áreas hasta de alrededor de 1 millón de pies cuadrados, unas 10 hectáreas. El otro grupo son casas entre 1000 millones y 3000 millones con áreas hasta de 0.2 millónes de pies cuadrados, unas 2 hectáreas. Inicialmente se construirá un primer modelo con ambos grupos juntos, y se evaluará su desempeño.

Feature Engineering¶

Transformacion de Variables¶

In [41]:
# Diccionario donde se almacenaran los transformadores
transformers = {}

Numericas a binarias¶

In [42]:
# Se considerará que un apartamento tiene sótano si sqft_basement está nulo.

du.data['tiene_sotano'] = du.data['sqft_basement'].notna()
# Reemplazando true por 1 y false por 0
du.data['tiene_sotano'] = du.data['tiene_sotano'].astype('int').astype('category')
du.data.drop(columns='sqft_basement', inplace=True)

Logaritmica¶

In [43]:
# Se transformarán las siguientes variables: sqft_above, sqft_living15, sqft_lot, price, sqft_lot15, sqft_living
columnas = ['sqft_above', 'sqft_living15', 'sqft_lot', 'price', 'sqft_lot15', 'sqft_living']
pt = PowerTransformer(method='box-cox')
pt.fit(du.data[columnas])
transformers['PowerTransformer'] = pt
du.data[columnas] = pt.transform(du.data[columnas])
In [44]:
profiler2 = ProfileReport(du.data[columnas], explorative=True)
profiler_to_file(profiler2, 'power_transform.html')
Out[44]:
True
In [45]:
# Se revisará nuevamente la distribución de las variables con box plots e histogramas
for variable_numerica in columnas:
    graficar.box(du.data[columnas], y=variable_numerica).show()

for variable_numerica in columnas:
    graficar.histogram(du.data[columnas], x=variable_numerica, nbins=20).show()
In [46]:
calcular_descriptivas(du.data[columnas])
Out[46]:
sqft_above sqft_living15 sqft_lot price sqft_lot15 sqft_living
count 4.384000e+03 4.384000e+03 4.384000e+03 4.384000e+03 4.384000e+03 4.384000e+03
mean -1.831868e-15 1.236237e-14 -2.129683e-15 -1.020752e-12 5.262619e-15 -1.329026e-16
std 1.000114e+00 1.000114e+00 1.000114e+00 1.000114e+00 1.000114e+00 1.000114e+00
min -3.728512e+00 -3.363023e+00 -3.780775e+00 -4.669308e+00 -3.862709e+00 -3.467887e+00
25% -6.910058e-01 -6.838228e-01 -4.741043e-01 -6.054371e-01 -4.934459e-01 -6.860350e-01
50% -4.132344e-02 -4.577595e-02 3.529345e-02 5.071293e-02 6.845990e-02 6.785413e-04
75% 7.417311e-01 7.214138e-01 4.155467e-01 6.217542e-01 4.149351e-01 7.011788e-01
max 3.333081e+00 3.039771e+00 3.917704e+00 2.901085e+00 4.097453e+00 4.561636e+00
rango 7.061593e+00 6.402794e+00 7.698479e+00 7.570393e+00 7.960162e+00 8.029523e+00
IQR 1.432737e+00 1.405237e+00 8.896511e-01 1.227191e+00 9.083810e-01 1.387214e+00
coef de var -5.459531e+14 8.089984e+13 -4.696069e+14 -9.797820e+11 1.900411e+14 -7.525165e+15
skewness 1.496731e-02 9.800042e-03 -1.026882e-01 2.751068e-02 -1.151754e-01 -1.103435e-03
kurtosis -4.306862e-01 -3.124572e-01 2.002935e+00 1.346496e+00 2.226061e+00 -1.785442e-01

Después de realizar la transformación de las variables, se encuentra que sqft_lot y sqft_lot15 siguen teniendo una alta kurtosis, por lo tanto se procederá a convertirlas en variables categóricas.

In [47]:
columnas_a_categoricas = ['sqft_lot', 'sqft_lot15']
kbd = KBinsDiscretizer(strategy='kmeans', encode='ordinal')
kbd.fit(du.data[columnas_a_categoricas])
transformers['KBinsDiscretizer'] = kbd
du.data[columnas_a_categoricas] = kbd.transform(du.data[columnas_a_categoricas])
du.data[columnas_a_categoricas] = du.data[columnas_a_categoricas].astype('category')

Analisis Univariable y Bivariable Final¶

In [48]:
profiler3 = ProfileReport(du.data, explorative=True)
In [49]:
profiler_to_file(profiler3, '2_0_transformacion_final.html')
profiler3
Summarize dataset:   0%|          | 0/5 [00:00<?, ?it/s]
Generate report structure:   0%|          | 0/1 [00:00<?, ?it/s]
Render HTML:   0%|          | 0/1 [00:00<?, ?it/s]
Out[49]:

In [50]:
df= du.data[['grade','view','condition']]
sns.distplot(df)
C:\Users\jevo1\Documents\Python Scripts\trabajo_ciencia_de_datos_1\venv\lib\site-packages\seaborn\distributions.py:2619: FutureWarning:

`distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).

Out[50]:
<AxesSubplot:ylabel='Density'>
In [51]:
du.data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4384 entries, 32828 to 235
Data columns (total 15 columns):
 #   Column         Non-Null Count  Dtype   
---  ------         --------------  -----   
 0   index          4384 non-null   int64   
 1   grade          4384 non-null   category
 2   view           4384 non-null   category
 3   bathrooms      4384 non-null   float64 
 4   bedrooms       4384 non-null   float64 
 5   sqft_above     4384 non-null   float64 
 6   sqft_living15  4384 non-null   float64 
 7   waterfront     4384 non-null   category
 8   floors         4384 non-null   float64 
 9   sqft_lot       4384 non-null   category
 10  price          4384 non-null   float64 
 11  condition      4384 non-null   category
 12  sqft_lot15     4384 non-null   category
 13  sqft_living    4384 non-null   float64 
 14  tiene_sotano   4384 non-null   category
dtypes: category(7), float64(7), int64(1)
memory usage: 339.7 KB

Variables de entrada¶

  • grade
  • view
  • bathrooms
  • bedrooms
  • sqft_above
  • sqft_living15
  • waterfront
  • floors
  • sqft_lot
  • condition
  • sqft_lot15
  • sqft_living
  • tiene_sotano

Variables de salida¶

  • price

MODELAMIENTO DE LOS DATOS (MACHINE LEARNING)¶

In [52]:
variables_entrada = ['grade', 'view', 'bathrooms', 'bedrooms', 'sqft_above', 'sqft_living15', 'waterfront', 'floors', 'sqft_lot', 'condition', 'sqft_lot15', 'sqft_living', 'tiene_sotano']
variable_salida  = 'price'
du.data = du.data[variables_entrada + [variable_salida]]
# Variables categóricas con las que se realizará onehotencoding
columnas_one_hot = ['grade', 'view', 'waterfront', 'sqft_lot', 'condition', 'sqft_lot15', 'tiene_sotano']
ohe = OneHotEncoder(sparse=False)
In [53]:
ohe.fit(du.data[columnas_one_hot])
transformers['OneHotEncoder'] = ohe
temp = pd.DataFrame(ohe.transform(du.data[columnas_one_hot]), columns=ohe.get_feature_names_out(), index=du.data.index).copy()
du.data.drop(columns=columnas_one_hot, inplace=True)
C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\3481676883.py:4: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

In [54]:
du.data = pd.concat([du.data, temp], axis=1)
In [55]:
y_name = 'price'
x_names = [columna for columna in du.data.columns if not columna == 'price']

Validacion y Evaluacion Cruzada (k-fold Cross Validation)¶

Se hace seleccion de los mejores modelos usando el Training Set y k-fold Cross Validation

In [56]:
modelos_a_probar = [('SGD',SGDRegressor()), ('SVR',SVR())]
In [57]:
scores = {}
for modelo in modelos_a_probar:
    scores[modelo[0]] = {}
    scores[modelo[0]]['scores'] = cross_val_score(modelo[1], du.data[x_names], du.data[y_name], cv=5, scoring='r2')
    scores[modelo[0]]['media'] = np.mean(scores[modelo[0]]['scores'])
    scores[modelo[0]]['std'] = np.std(scores[modelo[0]]['scores'])
    scores[modelo[0]]['coef. var.'] = scores[modelo[0]]['std'] / scores[modelo[0]]['media']
In [58]:
modelos_a_probar[0][1].fit(du.data[x_names], du.data[y_name])
modelos_a_probar[1][1].fit(du.data[x_names], du.data[y_name])
Out[58]:
SVR()
In [59]:
y_predict = {}
y_predict['SGD'] = modelos_a_probar[0][1].predict(du.data[x_names])
y_predict['SVR'] = modelos_a_probar[1][1].predict(du.data[x_names])
In [60]:
graficar.scatter(pd.DataFrame({'y_real': du.data[y_name], 'y_pred': y_predict['SGD']}), x='y_real', y='y_pred').show()

Optimizacion de Hiper parametros (Hyper Parameter optimization)¶

Se seleccionan solo los mejores modelos para realizar el ajuste de hiperparametros, ya que tiene una carga computacional alta.

Al final se obtienen los parametros del mejor modelo

In [ ]:
 

Evaluacion final del modelo con el Test set¶

Tomar los parametros obtenidos en el paso anterior, se crea el modelo con esos pararmetros y se entrena el modelo con todos los datos del Train -set

Finalmente se realiza la evaluacion (segun su problema si es de regresion o de clasificacion) usando el Test - set para definir si el modelo obtenido esta bien. Compare los resultados con el Train -set vs los resultados con el Test - set

In [ ]:
 

Implementacion del Modelo (Deploying)¶

Con el análisis básico y el ajuste hecho, comienza el trabajo real (ingeniería).

El último paso para poner en produccion el modelo de prediccion sera:

  1. Entrenarlo en todo el conjunto de datos nuevamente, para hacer un uso completo de todos los datos disponibles.
  2. Usar los mejores parámetros encontrados mediante la validación cruzada, por supuesto. Esto es muy similar a lo que hicimos al principio, pero esta vez teniendo una idea de su comportamiento y estabilidad. La evaluación se realizó con honestidad, en divisiones distintas de entrenamiento / prueba.

El predictor final se puede serializar y grabar en el disco, de modo que la próxima vez que lo usemos, podemos omitir todo el entrenamiento y usar el modelo capacitado directamente:

In [62]:
#import pickle # Esta es una libreria de serializacion nativa de python, puede tener problemas de seguridad
from joblib import dump # libreria de serializacion

# garbar el modelo en un archivo
#dump(Modelo_final, 'Nombre_Archivo_Modelo.joblib')
In [ ]:
 

Comunicacion de Resultados (Data Story Telling)¶

In [ ]:
 

Conclusiones¶

In [ ]:
 

Ayudas Y Referencias¶

  • https://medium.com/@joserzapata/paso-a-paso-en-un-proyecto-machine-learning-bcdd0939d387

  • Proyecto de Principio a Final sobre readmision de pacientes con Diabetes

  • a-complete-machine-learning-walk-through-in-python-part-one

  • a-starter-pack-to-exploratory-data-analysis-with-python-pandas-seaborn-and-scikit-learn

  • a-data-science-for-good-machine-learning-project-walk-through-in-python-part-one

  • Ejemplos de Kaggle

  • END to END ML from data colletion to deployment

Docente: Jose R. Zapata

  • https://joserzapata.github.io
  • https://twitter.com/joserzapata
  • https://www.linkedin.com/in/jose-ricardo-zapata-gonzalez/